﻿using Actor.Net.Binder;
using Actor.Net.Location;
using Actor.Net.Message;
using Actor.Net.Network.Actor;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Actor.Net
{
    /// <summary>
    /// Actor位置服务客户端
    /// </summary>
    public sealed class ActorLocationClient
    {
        private Dictionary<string, GroupInfo> LocalGroupInfoServerGroupKeyMapping { get; } = new Dictionary<string, GroupInfo>();
        private Dictionary<string, GroupInfo> LocalGroupInfoServerGroupMapping { get; } = new Dictionary<string, GroupInfo>();
        private ConcurrentDictionary<string, LocationContext> LocalLocationContextServerGroupMapping { get; } = new ConcurrentDictionary<string, LocationContext>();

        private HashSet<string> ServerGroupList { get; } = new HashSet<string>();

        private ConcurrentDictionary<string, ConcurrentDictionary<string, GroupInfo>> HostGroupInfoMapping { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<string, GroupInfo>>();
        private ConcurrentDictionary<string, GroupInfo> ServerGroupInfoMapping { get; } = new ConcurrentDictionary<string, GroupInfo>();
        private ConcurrentDictionary<string, ActorChannelContext> ChannelContextHostMapping { get; } = new ConcurrentDictionary<string, ActorChannelContext>();

        //ActorContext缓存
        private ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>> ActorContextServerGroupMapping { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>>();
        private ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>> ActorContextTypeMapping { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>>();
        private ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>> ActorContextHostMapping { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<string, LocationContext>>();
        private ConcurrentDictionary<int, ConcurrentDictionary<string, LocationContext>> ActorContextChannelMapping { get; } = new ConcurrentDictionary<int, ConcurrentDictionary<string, LocationContext>>();
        private ConcurrentDictionary<string, LocationContext> ActorContextMapping { get; } = new ConcurrentDictionary<string, LocationContext>();

        //ServerNetwork
        private ConcurrentDictionary<string, ActorNetwork> ServerNetworkList { get; } = new ConcurrentDictionary<string, ActorNetwork>();

        private ActorNetwork LocaltionClient { get; set; }
        private ActorChannelContext ConnectChannelContext => this.LocaltionClient.NetService.ConnectChannel.ChannelContext;
        private LocalActorServer LocalServer { get; set; }

        /// <summary>
        /// 单例
        /// </summary>
        public static ActorLocationClient Instance { get; } = new ActorLocationClient();

        /// <summary>
        /// 私有构造
        /// </summary>
        private ActorLocationClient() { }

        /// <summary>
        /// 本地服务Ip地址
        /// </summary>
        public string LocalServerIpAddress => LocalServer.IpAddress;
        /// <summary>
        /// 本地服务端口
        /// </summary>
        public int LocalServerPort => LocalServer.Port;
        /// <summary>
        /// 未能捕获异常处理者
        /// </summary>
        public Action<ActorChannelContext, Exception> ExceptionHandler { get; set; }
        /// <summary>
        /// 未能捕获异常处理者
        /// </summary>
        public Action ActorClientConnected { get; set; }

        /// <summary>
        /// 添加一个Actor服务分组
        /// </summary>
        /// <param name="serverGroup"></param>
        internal void AddServerGroup(string serverGroup)
        {
            ServerGroupList.Add(serverGroup);
        }

        /// <summary>
        /// 添加本地服务
        /// </summary>
        /// <param name="localServer"></param>
        internal void AddLocalActorServer(LocalActorServer localServer)
        {
            if (localServer == null)
                return;

            LocalServer = localServer;

            foreach (var serverGroup in ServerGroupList)
            {
                var localGroupInfo = new GroupInfo
                {
                    ServerGroup = serverGroup,
                    IpAddress = LocalServerIpAddress,
                    Port = LocalServerPort,
                };
                var serverGroupKey = KeyGenerator.CreateServerGroupKey(localGroupInfo);
                LocalGroupInfoServerGroupKeyMapping[serverGroupKey] = localGroupInfo;
                LocalGroupInfoServerGroupMapping[localGroupInfo.ServerGroup] = localGroupInfo;
            }
        }

        /// <summary>
        /// 添加异常处理
        /// </summary>
        /// <param name="handlerActor"></param>
        internal void AddExceptionHandler(Action<ActorChannelContext, Exception> handlerActor)
        {
            ExceptionHandler += handlerActor;

            if (LocalServer != null)
                LocalServer.ActorNetworkServer.AddSocketErrorHandler(ExceptionHandler);
        }


        /// <summary>
        /// 添加Actor启动成功事件
        /// </summary>
        /// <param name="handlerActor"></param>
        internal void AddActorStartHandler(Action handlerActor)
        {
            this.ActorClientConnected += handlerActor;
        }

        /// <summary>
        /// 连接位置服务服务端
        /// </summary>
        public void Connect()
        {
            if (!ServerGroupList.Any())
                throw new Exception("Server group info can not be empty.");

            this.LocaltionClient = new ActorNetwork(ActorProviderService.LocationIpAddress, ActorProviderService.LocationPort)
            {
                //IsSynchronizationContext = false
            };

            this.LocaltionClient.Bind<RegisterActorLocationAdapter>(CommandRetention.ACTOR_REGISTER_FLAG);
            this.LocaltionClient.Bind<RemoveActorLocationAdapter>(CommandRetention.ACTOR_REMOVE_FLAG);
            this.LocaltionClient.Bind<RemoveGroupLocationAdapter>(CommandRetention.GROUPS_REMOVE_FLAG);
            this.LocaltionClient.Bind<UnRegisterActorLocationAdapter>(CommandRetention.ACTOR_UNREGISTER_FLAG);
            this.LocaltionClient.Bind<ActorMessageAdapter>(CommandRetention.ACTOR_MESSAGE_FLAG);

            this.LocaltionClient.AddSocketErrorHandler(ExceptionHandler);
            this.LocaltionClient.AddDisconnectedHandler((cx) => this.Clear());
            this.LocaltionClient.AddConnectedHandler((cx) => this.ActorClientConnected?.Invoke());
            LocaltionClient.Connect();
        }

        private void Clear()
        {
            LocalGroupInfoServerGroupKeyMapping.Clear();
            LocalGroupInfoServerGroupMapping.Clear();
            LocalLocationContextServerGroupMapping.Clear();
            HostGroupInfoMapping.Clear();
            ServerGroupInfoMapping.Clear();
            ChannelContextHostMapping.Clear();
            ActorContextServerGroupMapping.Clear();
            ActorContextTypeMapping.Clear();
            ActorContextHostMapping.Clear();
            ActorContextChannelMapping.Clear();
            ActorContextMapping.Clear();
            ServerNetworkList.Clear();
            KeyGenerator.Clear();
        }

        /// <summary>
        /// 添加Actor服务分组连接信息
        /// </summary>
        /// <param name="groupInfo"></param>
        public ActorChannelContext AddServerGroupConnection(GroupInfo groupInfo)
        {
            var hostKey = KeyGenerator.CreateHostKey(groupInfo.IpAddress, groupInfo.Port);
            var serverGroupKey = KeyGenerator.CreateServerGroupKey(groupInfo);
            if (ServerNetworkList.TryGetValue(serverGroupKey, out ActorNetwork gameNetwork))
                return gameNetwork.NetService.ConnectChannel.ChannelContext;

            var network = new ActorNetwork(groupInfo.IpAddress, groupInfo.Port)
            {
                //IsSynchronizationContext = false
            };

            network.Bind<ActorMessageAdapter>(CommandRetention.ACTOR_MESSAGE_FLAG);
            ServerNetworkList.AddOrUpdate(hostKey, network, (k, v) => network);
            network.AddConnectedHandler((context) =>
            {
                context.ActorHostKey = hostKey;
                this.AddConnectedChannelContext(serverGroupKey, hostKey, groupInfo, context);
                if (!HostGroupInfoMapping.TryGetValue(hostKey, out ConcurrentDictionary<string, GroupInfo> groupInfos))
                {
                    groupInfos = new ConcurrentDictionary<string, GroupInfo>();
                    HostGroupInfoMapping.AddOrUpdate(hostKey, groupInfos, (k, v) => groupInfos);
                }
                groupInfos.AddOrUpdate(groupInfo.ServerGroup, groupInfo, (k, v) => groupInfo);
            });
            network.AddDisconnectedHandler((context) =>
            {
                if (!HostGroupInfoMapping.TryRemove(context.ActorHostKey, out ConcurrentDictionary<string, GroupInfo> groupInfoValues))
                    return;

                foreach (var info in groupInfoValues.Values)
                    RemoveActorContextByGroup(info);

                if (!ActorContextHostMapping.TryRemove(context.ActorHostKey, out ConcurrentDictionary<string, LocationContext> hostContexts))
                    return;

                if (!ChannelContextHostMapping.TryRemove(context.ActorHostKey, out ActorChannelContext channelContext))
                    return;

                if (!ActorContextChannelMapping.TryRemove(channelContext.ChannelId, out ConcurrentDictionary<string, LocationContext> channelContexts))
                    return;
            });
            network.AddSocketErrorHandler(ExceptionHandler);
            return network.Connect();
        }

        private void AddConnectedChannelContext(string serverGroupKey, string hostKey, GroupInfo groupInfo, ActorChannelContext context)
        {
            if (ServerGroupInfoMapping.ContainsKey(serverGroupKey))
                return;

            ServerGroupInfoMapping.AddOrUpdate(serverGroupKey, groupInfo, (k, v) => groupInfo);
            ChannelContextHostMapping.AddOrUpdate(hostKey, context, (k, v) => context);
        }

        /// <summary>
        /// 删除一个Actor服务分组下所有Actor
        /// </summary>
        /// <param name="groupInfo"></param>
        public void RemoveActorContextByGroup(GroupInfo groupInfo)
        {
            var serverGroupKey = KeyGenerator.CreateServerGroupKey(groupInfo);
            if (!ServerGroupInfoMapping.TryRemove(serverGroupKey, out GroupInfo groupInfos))
                return;

            if (!ActorContextServerGroupMapping.TryRemove(serverGroupKey, out ConcurrentDictionary<string, LocationContext> serverGroupContexts))
                return;

            foreach (var kv in serverGroupContexts)
                RemoveContext(kv.Value.LocationInfo);
        }

        /// <summary>
        /// 获取一个Actor位置信息上下文
        /// </summary>
        /// <param name="routerInfo"></param>
        /// <returns></returns>
        public async Task<LocationContext> GetLocationContext(ActorRouterInfo routerInfo)
        {
            var actorKey = KeyGenerator.CreateActorKey(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.ActorId);
            if (ActorContextMapping.TryGetValue(actorKey, out LocationContext context))
                return context;

            var locationClientContext = this.ConnectChannelContext;
            if (locationClientContext == null)
                throw new Exception("No location service connection.");

            var locationContext = GetActorContext(actorKey);
            if (locationContext != null)
                return locationContext;

            var locationInfo = await locationClientContext.CallAsync<ActorLocationInfo>(routerInfo, CommandRetention.ACTOR_GET_LOCATION_FLAG);
            if (locationInfo.Stated != ActorLocationState.Successful)
                return null;

            return AddContext(locationInfo);
        }

        /// <summary>
        /// 获取一个Actor位置信息上下文
        /// </summary>
        /// <param name="routerInfos"></param>
        /// <returns></returns>
        public async Task<List<LocationContext>> GetLocationContexts(List<ActorRouterInfo> routerInfos)
        {
            var locationClientContext = this.ConnectChannelContext;
            if (locationClientContext == null)
                throw new Exception("No location service connection.");

            var routerList = new List<ActorRouterInfo>();
            foreach (var routerInfo in routerInfos)
            {
                var actorKey = KeyGenerator.CreateActorKey(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.ActorId);
                if (ActorContextMapping.TryGetValue(actorKey, out LocationContext context))
                    continue;

                var locationContext = GetActorContext(actorKey);
                if (locationContext != null)
                    continue;

                routerList.Add(routerInfo);
            }

            List<LocationContext> contexts = new List<LocationContext>();
            var locationInfos = await locationClientContext.CallAsync<List<ActorLocationInfo>>(routerList, CommandRetention.ACTOR_GET_LOCATION_LIST_FLAG);
            foreach(var locationInfo in locationInfos)
            {
                if (locationInfo.Stated != ActorLocationState.Successful)
                    continue;

                var context = AddContext(locationInfo);
                contexts.Add(context);
            }

            return contexts;
        }

        /// <summary>
        /// 注册本地Actor位置信息
        /// </summary>
        /// <param name="routerInfo"></param>
        /// <returns></returns>
        public async Task<LocationContext> RegisterLocalActor(ActorRouterInfo routerInfo)
        {
            if (!LocalGroupInfoServerGroupMapping.ContainsKey(routerInfo.ServerGroup))
                throw new ArgumentNullException($"Not bind server group {routerInfo.ServerGroup}.");

            var actorKey = KeyGenerator.CreateActorKey(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.ActorId);
            if (ActorContextMapping.TryGetValue(actorKey, out LocationContext context))
                return context;

            var groupInfo = LocalGroupInfoServerGroupMapping[routerInfo.ServerGroup];
            var locationInfo = new ActorLocationInfo
            {
                ServerGroup = routerInfo.ServerGroup,
                ActorType = routerInfo.ActorType,
                ActorId = routerInfo.ActorId,
                IpAddress = groupInfo.IpAddress,
                Port = groupInfo.Port,
            };

            var locationClientContext = this.ConnectChannelContext;
            if (locationClientContext == null)
                throw new Exception("No location service connection.");

            await locationClientContext.CallAsync<ActorLocationInfo>(locationInfo, CommandRetention.ACTOR_REGISTER_FLAG, CancellationToken.None);
            return AddContext(locationInfo);
        }

        /// <summary>
        /// 取消Actor位置信息注册
        /// </summary>
        /// <param name="routerInfo"></param>
        public void UnRegisterActor(ActorRouterInfo routerInfo)
        {
            var actorKey = KeyGenerator.CreateActorKey(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.ActorId);
            if (ActorContextMapping.TryGetValue(actorKey, out LocationContext locationContext))
            {
                RemoveContext(locationContext.LocationInfo);
            }

            var locationClientContext = this.ConnectChannelContext;
            if (locationClientContext == null)
                throw new Exception("No location service connection.");

            var groupInfo = LocalGroupInfoServerGroupMapping[routerInfo.ServerGroup];
            locationClientContext.Send(new ActorLocationInfo 
            {
                ServerGroup = routerInfo.ServerGroup,
                ActorType = routerInfo.ActorType,
                IpAddress = groupInfo.IpAddress,
                ActorId = routerInfo.ActorId,
                Port = groupInfo.Port,
            }, CommandRetention.ACTOR_UNREGISTER_FLAG);
        }

        /// <summary>
        /// 获取Actor位置信息注册
        /// </summary>
        /// <param name="actorKey"></param>
        public LocationContext GetActorContext(string actorKey)
        {
            ActorContextMapping.TryGetValue(actorKey, out LocationContext locationContext);
            return locationContext;
        }

        /// <summary>
        /// 删除Actor位置信息上下文
        /// </summary>
        /// <param name="locationInfo"></param>
        public void RemoveContext(ActorLocationInfo locationInfo)
        {
            if (locationInfo == null)
                return;

            var hostKey = KeyGenerator.CreateHostKey(locationInfo);
            var actorKey = KeyGenerator.CreateActorKey(locationInfo);
            var actorTypeKey = KeyGenerator.CreateActorTypeKey(locationInfo);
            var serverGroupKey = KeyGenerator.CreateServerGroupKey(locationInfo.ServerGroup, locationInfo.IpAddress, locationInfo.Port);

            ActorBinder.Remove(locationInfo.ServerGroup, locationInfo.ActorType, locationInfo.ActorId);
            var type = ActorBinder.GetActorType(locationInfo.ServerGroup, locationInfo.ActorType);
            ActorProviderHelper.RemoveActorStringId(type, locationInfo.ActorId);

            ActorContextMapping.TryRemove(actorKey, out _);

            if (ActorContextServerGroupMapping.TryGetValue(serverGroupKey, out ConcurrentDictionary<string, LocationContext> groupContexts))
                groupContexts.TryRemove(actorKey, out _);

            if (ActorContextTypeMapping.TryGetValue(actorTypeKey, out ConcurrentDictionary<string, LocationContext> typeContexts))
                typeContexts.TryRemove(actorKey, out _);

            if (ActorContextHostMapping.TryGetValue(hostKey, out ConcurrentDictionary<string, LocationContext> hostContexts))
                hostContexts.TryRemove(actorKey, out _);

            if (!ChannelContextHostMapping.TryGetValue(hostKey, out ActorChannelContext channelContext))
                return;

            if (ActorContextChannelMapping.TryGetValue(channelContext.ChannelId, out ConcurrentDictionary<string, LocationContext> channelContexts))
                channelContexts.TryRemove(actorKey, out _);
        }

        private LocationContext AddContext(ActorLocationInfo locationInfo)
        {
            var hostKey = KeyGenerator.CreateHostKey(locationInfo);
            var actorKey = KeyGenerator.CreateActorKey(locationInfo);
            var actorTypeKey = KeyGenerator.CreateActorTypeKey(locationInfo);
            var serverGroupKey = KeyGenerator.CreateServerGroupKey(locationInfo.ServerGroup, locationInfo.IpAddress, locationInfo.Port);

            if (ActorContextMapping.TryGetValue(actorKey, out LocationContext value))
                return value;

            //如果Actor是本地服务创建的，不需要通讯管道上下文映射，在本地服务调用Actor服务直接是对象调用
            if (LocalGroupInfoServerGroupKeyMapping.ContainsKey(serverGroupKey))
            {
                if (!LocalLocationContextServerGroupMapping.TryGetValue(locationInfo.ServerGroup, out LocationContext locationContext))
                {
                    locationContext = new LocationContext(locationInfo, null);
                    LocalLocationContextServerGroupMapping.AddOrUpdate(locationInfo.ServerGroup, locationContext, (k, v) => locationContext);
                }
                return locationContext;
            }

            if (!ChannelContextHostMapping.TryGetValue(hostKey, out ActorChannelContext channelContext))
            {
                var serverGroup = new GroupInfo
                {
                    ServerGroup = locationInfo.ServerGroup,
                    IpAddress = locationInfo.IpAddress,
                    Port = locationInfo.Port,
                };

                channelContext = AddServerGroupConnection(serverGroup);
                ChannelContextHostMapping.AddOrUpdate(hostKey, channelContext, (k, v) => channelContext);
            }

            var localtionContext = new LocationContext(locationInfo, channelContext);
            ActorContextMapping.AddOrUpdate(actorKey, localtionContext, (k, v) => localtionContext);

            if (!ActorContextServerGroupMapping.TryGetValue(serverGroupKey, out ConcurrentDictionary<string, LocationContext> groupContexts))
            {
                groupContexts = new ConcurrentDictionary<string, LocationContext>();
                ActorContextServerGroupMapping.AddOrUpdate(serverGroupKey, groupContexts, (k, v) => groupContexts);
            }
            groupContexts.AddOrUpdate(actorKey, localtionContext, (k, v) => localtionContext);

            if (!ActorContextTypeMapping.TryGetValue(actorTypeKey, out ConcurrentDictionary<string, LocationContext> typeContexts))
            {
                typeContexts = new ConcurrentDictionary<string, LocationContext>();
                ActorContextTypeMapping.AddOrUpdate(actorTypeKey, typeContexts, (k, v) => typeContexts);
            }
            typeContexts.AddOrUpdate(actorKey, localtionContext, (k, v) => localtionContext);

            if (!ActorContextHostMapping.TryGetValue(hostKey, out ConcurrentDictionary<string, LocationContext> hostContexts))
            {
                hostContexts = new ConcurrentDictionary<string, LocationContext>();
                ActorContextHostMapping.AddOrUpdate(hostKey, hostContexts, (k, v) => hostContexts);
            }
            hostContexts.AddOrUpdate(actorKey, localtionContext, (k, v) => localtionContext);

            var channelId = channelContext.ChannelId;
            if (!ActorContextChannelMapping.TryGetValue(channelId, out ConcurrentDictionary<string, LocationContext> channelContexts))
            {
                channelContexts = new ConcurrentDictionary<string, LocationContext>();
                ActorContextChannelMapping.AddOrUpdate(channelId, channelContexts, (k, v) => channelContexts);
            }
            channelContexts.AddOrUpdate(actorKey, localtionContext, (k, v) => localtionContext);

            return localtionContext;
        }
    }
}
